package roborally.model;

import roborally.exceptions.InvalidPositionException;
import be.kuleuven.cs.som.annotate.*;

/**
 * A class for elements involving a board and a position.
 * 
 * @invar	If an element isn't associated with any board, it hasn't a position either.
 * 			| if(getBoard() == null)
 * 			|	then getPosition() == null
 * @invar	If an element is associated with a board, it has a proper position for this board or no position.
 * 			| if(getBoard() != null)
 * 			|	then ( getBoard().isValidPosition(getPosition()) || getPosition() == null )
 * 
 * @version	1.0
 * @author  Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders		(1e Bach Informatica)
 *
 */
public abstract class Element extends Terminatable{
	
	/**
	 * Initialize this new element without a board or a position yet.
	 * 
	 */
	public Element() {}
	
	/**
	 * Initialize this new element with a given board and no position.
	 * 
	 * @param	board
	 * 			The board for this new element.
	 * @effect	...
	 * 			| this()
	 * @effect	...
	 * 			| setBoard(board)
	 * 
	 */
	public Element(Board board){
		this();
		setBoard(board);
	}
	
	/**
	 * Initialize this new element with a given board and position.
	 * 
	 * @param 	board
	 * 			The board for this new element.
	 * @param	position
	 * 			The position for this new element.
	 * @effect	...
	 * 			| this(board)
	 * @effect	...
	 * 			| setPosition(position)
	 * @throws 	InvalidPositionException
	 * 			| board != null && board.isValidPosition(position)
	 */
	public Element(Board board, Position position) throws InvalidPositionException {
		this(board);
		setPosition(position);
	}
	
	/**
	 * Variable registering the board.
	 */
	private Board board;
	
	/**
	 * Return the board of this element.
	 * Return NULL if the element is not located on a board.
	 */
	@Basic @Raw
	public Board getBoard(){
		return board;
	}
	
	/**
	 * Set the board of this element to a given board.
	 * 
	 * @param	board
	 * 			The board where the element is located.
	 * @post	...
	 * 			| new.getBoard() == board
	 * @throws	IllegalStateException
	 * 			This element is terminated.
	 * 			| isTerminated()
	 */
	@Model @Raw
	void setBoard(Board board) throws IllegalStateException {
		if(isTerminated())
			throw new IllegalStateException("This element is terminated!");
		this.board = board;
	}
	
	/**
	 * Variable registering the position.
	 */
	private Position position;
	
	/**
	 * Return the position of the element.
	 * Return NULL if the element has no position.
	 */
	@Basic @Raw
	public Position getPosition(){
		return position;
	}
	
	/**
	 * Set the position of this element to a given position.
	 * 
	 * @param	position
	 * 			The new position for this element.
	 * @post	...
	 * 			| new.getPosition() == position
	 * @throws	IllegalStateException
	 * 			This element is terminated.
	 * 			| isTerminated()
	 * @throws	IllegalStateException
	 * 			It is impossible to give a position to an element 
	 * 			which isn't located on a board.
	 * 			| getBoard() == null && position != null
	 * @throws 	InvalidPositionException
	 * 			The new position is an invalid position 
	 * 			for the board this element is located on.
	 * 			| getBoard() != null && !getBoard().isValidPosition(position)
	 */
	@Model @Raw
	void setPosition(Position position) throws InvalidPositionException, IllegalStateException {
		if(isTerminated())
			throw new IllegalStateException("This element is terminated!");
		if(getBoard() == null && position != null)
			throw new IllegalStateException("It is impossible to give a position to an element which isn't located on a board.");
		if(getBoard() != null && !getBoard().isValidPosition(position))
			throw new InvalidPositionException(position);
		
		this.position = position;
	}
	
	/**
	 * Check whether this element can share its position with a second element.
	 * 
	 * @param 	other
	 * 			The element to check for whether it can share its position with the prime element.
	 * @return	...
	 * 			| if(other == null)
	 * 			| 	then result == true
	 * @return	...
	 * 			| if(other == this)
	 * 			| 	then result == true
	 * @return	...
	 * 			| result == canSharePositionWithCore(other) || other.canSharePositionWithCore(this)
	 */
	public boolean canSharePositionWith(Element other){
		if(other == null)
			return true;
		if(other == this)
			return true;
		return canSharePositionWithCore(other) || other.canSharePositionWithCore(this);
	}
	
	/**
	 * Check whether this element can share its position with a collection of elements. 
	 * 
	 * @param	list
	 * 			The list of elements to check for whether the prime element can share a position with them.
	 * @return	...
	 * 			| for each element in list:
	 * 			| 	if(this.canSharePositionWith(element))
	 * 			|		then result == true
	 */
	public boolean canSharePositionWith(Iterable<Element> list){
		for(Element element : list)
			if(!canSharePositionWith(element))
				return false;
		return true;
	}
	
	/**
	 * Check whether this element can share its position with a second element. 
	 * 
	 * @param 	other
	 * 			The element to check for whether it can share its position with the prime element.
	 */
	public abstract boolean canSharePositionWithCore(Element other);
	
	/**
	 * Terminate this element.
	 * 
	 * @effect	...
	 * 			| if(getBoard() != null)
	 * 			| 	then getBoard().removeElement(this)
	 * @effect	...
	 *       	| super.terminate()
	 */
	@Override
	public void terminate(){
		try {
			getBoard().removeElement(this);
		}catch (NullPointerException e)	{
			// This element isn't associated with a board.
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
		
		super.terminate();
	}
}
